"""--------------------------------------------------------------------------------------------------
Filename:   bioapp.py
Author:     Ben Han
Date:       20-Apr-2010
Description:
    Biofeedback application for strength/skill training in Dysphagia patients.
--------------------------------------------------------------------------------------------------"""

import os, sys, wx
import database
from controls import ControlPanel
from dialogs import BreakDialog, DatabaseDialog, PatientPanel, SessionPanel
from config import ConfigData, StateVariables
from myopace_serial import Console
import numpy as np
from liveplot import LivePlot


#===============================================================================
# CLASS: MainFrame(wx.Frame)
#    This class is the main frame used in the application.
#    It primarily acts as a container for the main menu and the control panel
#===============================================================================
class MainFrame(wx.Frame):
    """ The main frame of the application
    """
    def __init__(self, parent, ID, title, app):
        wx.Frame.__init__(self, parent, ID, title, pos=(0, 0), size=(600, 400))

        self.app = app
        self.cfg = app.cfg
        self.current = app.current

        # Create a menu bar
        self.menubar = wx.MenuBar()
        file_menu = wx.Menu()

        m_database = file_menu.Append(-1, "&Database options\tCtrl-D", "Edit patient details or change current patient")
        self.Bind(wx.EVT_MENU, self.on_database, m_database)
        file_menu.AppendSeparator()

        m_exit = file_menu.Append(-1, "E&xit\tCtrl-X", "Exit")
        self.Bind(wx.EVT_MENU, self.app.on_exit, m_exit)

        self.menubar.Append(file_menu, "&File")
        self.SetMenuBar(self.menubar)

        # Create a statusbar
        self.statusbar = self.CreateStatusBar()

        # Set handler for the close button on the window.
        self.Bind(wx.EVT_CLOSE, self.app.on_exit)

    def status_message(self, msg):
        self.statusbar.SetStatusText(msg)
        
    def on_database(self, event):
        self.db_frame = DatabaseDialog(self, -1, self.app.current)
        self.db_frame.Show()
        


#===============================================================================
# CLASS: MenuPanel(wx.Panel)
#    This class contains the main menu buttons with references for the
#    associated event handlers. These event handlers are contained in the 
#    MyApp class.
#===============================================================================
class MenuPanel(wx.Panel):
    def __init__(self, parent, ID, config):
        wx.Panel.__init__(self, parent, ID)

        self.parent = parent
        self.cfg = config

#------------------------------------------------------------------------------ Buttons
        # Create buttons
        self.str_button = wx.Button(self, 1, "Strength Training", size=(150, 40))
        self.skl_button = wx.Button(self, 3, "Skill Training", size=(150, 40))
        self.cfg_button = wx.Button(self, -1, "Configuration", size=(150, 40))
        self.ext_button = wx.Button(self, -1, "Exit Program", size=(150, 40))
        
        # Bind button click event with appropriate handlers
        self.Bind(wx.EVT_BUTTON, self.on_str_button, self.str_button)
        self.Bind(wx.EVT_BUTTON, self.on_skl_button, self.skl_button)
        self.Bind(wx.EVT_BUTTON, self.on_cfg_button, self.cfg_button)
        self.Bind(wx.EVT_BUTTON, self.on_ext_button, self.ext_button)

        # Create a box and sizer
        box = wx.StaticBox(self, -1, "Main Menu Panel")
        buttons = wx.StaticBoxSizer(box, wx.VERTICAL)

        # Add buttons to the sizer
        buttons.Add(self.str_button, 0, wx.ALL, border=5)
        buttons.Add(self.skl_button, 0, wx.ALL, border=5)
        buttons.Add(self.cfg_button, 0, wx.ALL, border=5)
        buttons.Add(self.ext_button, 0, wx.ALL, border=5)
        
#------------------------------------------------------------------------------ PatientPanel
        self.pat_panel = PatientPanel(self, -1)
        
#------------------------------------------------------------------------------ SessionPanel
        self.ses_panel = SessionPanel(self, -1, self.cfg)

        self.SetSizer(buttons)
        buttons.Fit(parent)

    def on_str_button(self, event):
        self.parent.app.on_training(event)

    def on_skl_button(self, event):
        self.parent.app.on_training(event)

    def on_cfg_button(self, event):
        self.cfg.display_frame(True)

    def on_ext_button(self, event):
        self.parent.app.on_exit(event)


"""--------------------------------------------------------------------------------------------------
  CLASS: MyApp
    Main application class
--------------------------------------------------------------------------------------------------"""
class MyApp(wx.PySimpleApp):
    def __init__(self):
        wx.PySimpleApp.__init__(self)
        
        # Instantiate a ConfigData object
        # Need to pass it a wx.Frame object as a parent
        self.cfg_frame = wx.Frame(None, -1, "Configuration")
        # bind a handler for the close event
        self.cfg_frame.Bind(wx.EVT_CLOSE, self._on_cfg_close)
        self.cfg = ConfigData(self.cfg_frame)
        
        # Create a state variable object to store program state
        self.current = StateVariables()

        # Create the main frame and place the main menu panel inside it
        self.main_frame = MainFrame(None, -1, "BioApp", self)
        self.menu_panel = MenuPanel(self.main_frame, -1, self.cfg)
        self.main_frame.Show()

    def on_training(self, event):
        # Check to see if a database is loaded
#        if not self.current.database:
#            self.status_msg('No database loaded')
#            dlg_txt = 'No database loaded.\n\n\
#Please create a new database or load an existing\n\
#database before starting a new session.'
#            dlg = wx.MessageDialog(None, dlg_txt, 'Notice', wx.OK | wx.ICON_ERROR)
#            dlg.ShowModal()
#        else:
#            if not self.current.patient:
#                self.status_msg('No patient loaded')
#                dlg_txt = 'No patient loaded.\n\n\
#Create a new patient or load an existing\n\
#patient before starting a new session.'
#                dlg = wx.MessageDialog(None, dlg_txt, 'Notice', wx.OK | wx.ICON_ERROR)
#                dlg.ShowModal()
#            else:
                # Handler for the strength/skill buttons on the main menu
        if event.GetId() == 1:
            self.status_msg("Strength training mode. Calibration required.")
            self.current.mode = 1       # Strength calibration
            type = 'strength'
        elif event.GetId() == 3:
            self.status_msg("Skill training mode. Calibration required.")
            self.current.mode = 3       # Skill calibration
            type = 'skill'
        self.setup_training(type)
        
    def setup_training(self, type):
        # Clear the menu frame then create a control panel to place inside it
        self.menu_panel.Destroy()
        self.ctrl = ControlPanel(self.main_frame, -1, self)

        # Create a new frame and panel. Place a plot inside it.
        self.cfg.save_config()
#        title = type + ' training' + ' - ' + self.current.patient.name + '-' + self.current.patient.id
        title = 'CHANGE THIS TITLE!!!'
        self.plot_frame = wx.Frame(None, -1, title, size=(700, 500), pos=(600, 0))
        self.current.plot_frame = True
        self.plot_frame.Bind(wx.EVT_CLOSE, self._on_plot_close)
        self.plot = LivePlot(self.plot_frame, -1, self)
        self.plot.init_plot()
        self.plot_frame.Show()
        
        # Pass the control panel a pointer to the plot object
        self.ctrl.plot = self.plot
        
        # Create a database.Session object to store data for the session
        self.current.session = database.Session(self.cfg, type)
#        self.current.patient.add_session(self.current.session)

    def end_block(self):
        # Report the results of the last block of trials
        print '\n>>> End Block <<<'
        print 'Block history follows:'
        for i in self.current.history:
            print i

        # Create a database.Block object, add current emgdata and history
        self.block = database.Block(self.plot.emgdata, self.current.history)
        self.block.calc()                       # calculate block stats
        self.current.session.add_block(self.block)      # add block to database.Sessions object
        self.current.history = []               # reset history for the next block
        if self.current.alive:
            self.ctrl.on_pause(None)                         # pause the data acquisition process

        # Start a break if this is not the last block
        if self.current.trial_index < self.cfg.trials_per_session:
            self.ctrl.but_ctrl.pause_button.Disable()
            self._start_break()

    def end_session(self):
        # Housekeeping at the end of the session
        print '\n>>> End Session <<<'
        self.current.session.calc()
        self.on_mainmenu(None)

    def parse_calib_data(self, data):
        mode = self.current.mode

        if mode == 1:
            if len(data) < 5:
                sys.stderr.write('\n The calibration is incomplete. Array should contain five values.\n')
                self.on_str_button(wx.EVT_BUTTON)
            else:
                self.calib_data = np.array(data)
                self.current.baseline = self.current.str_target = self.calib_data.mean()
                self.status_msg('Baseline for strength target is:' + repr(self.current.baseline))
                print '\nBaseline:', self.current.baseline

        elif mode == 3:
            if len(data) < 5:
                sys.stderr.write('\n The calibration is incomplete. Array should contain five values.\n')
                self.on_skl_button(wx.EVT_BUTTON)
            else:
                self.calib_data = np.array(data)
                self.current.baseline = self.calib_data.mean()
                self.status_msg('Baseline for upper bound is:' + repr(self.current.baseline))
                print '\nBaseline:', self.current.baseline
                self.current.min_range = [20, self.current.baseline * 0.2]
                self.current.max_range = [self.cfg._x_axis_max - 20, self.current.baseline]
                self.current.min_size = [10, 10]
                self.current.max_size = [self.current.max_range[0] - self.current.min_range[0], self.current.max_range[1] - self.current.min_range[1]]

    def status_msg(self, msg):
        self.main_frame.statusbar.SetStatusText(msg)

    def display_status(self):
        mode = self.current.mode
        s = ''
        if mode == 1:
            s = 'Strength mode    CALIBRATION'
        elif mode == 2:
            s = 'Strength mode    Trial: %i' % self.current.trial_index
        elif mode == 3:
            s = 'Skill mode       CALIBRATION'
        elif mode == 4:
            s = 'Skill mode       Trial: %i' % self.current.trial_index
        self.status_msg(s)
        
    def open_serial(self):
        # Start serial thread
        if not self.current.serial_open:
            # if the serial port is not open the serial comms channel
            try:
                self.console = Console(self.plot.update_data, self.cfg._port)
                self.current.serial_open = True
            except:
                sys.stderr.write("-- Error opening serial channel for communication with device.\n")
                sys.stderr.write("-- Please check connections and settings.\n")
                sys.stderr.write("-- Exception type:\t%s\n" % sys.exc_info()[0])
        else:
            pass
        
    def on_mainmenu(self, event):
        # This function is run when the Main Menu button is clicked
        # Closes the control panel and plot windows then returns to the main
        # menu dialog.
        dialog_text = "Quit to main menu?\n\nClick \"Yes\" to discard any unsaved data.\nClick \"No\" to return."
        dial = wx.MessageDialog(None, dialog_text, 'Warning',
                                wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
        ret = dial.ShowModal()
        if ret == wx.ID_YES:
            if self.current.serial_open:
                self.console.stop()
                self.current.serial_open = False
    
            if self.current.plot_frame:
                self.plot_frame.Destroy()
                self.current.plot_frame = False
    
            self._reset_session()
            self.ctrl.Destroy()
            self.menu_panel = MenuPanel(self.main_frame, -1, self.cfg)
            self.main_frame.SetTitle('BioApp')
            self.status_msg('Main Menu')
        else:
            pass
                
    def on_exit(self, event):
        if not self.current.patient:
            pass
        elif not self.current.patient.saved:
            dialog_text = "Save patient file?"
            dial = wx.MessageDialog(None, dialog_text, 'Question',
                                    wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
            ret = dial.ShowModal()
            if ret == wx.ID_YES:
                self.main_frame.db_frame = DatabaseDialog(self.main_frame, -1, self.current)
                self.main_frame.db_frame.on_save(None)
        
        # Kill all processes
        if self.current.serial_open:
            self.console.stop()
            self.current.serial_open = False

        if self.current.plot_frame:
            self.plot_frame.Destroy()
            self.current.plot_frame = False

        self.main_frame.Destroy()
        self.cfg_frame.Destroy()

    def _start_break(self):
        self.break_frame = BreakDialog(self.ctrl, -1, self._on_timer, self.cfg._break_len)
        self.break_frame.start()

    def _on_timer(self):
        # When the timer hits the set break time, end break
        print "Break timer ended."
        if not self.current.alive:
            self.ctrl.on_pause(None)
        self.ctrl.but_ctrl.pause_button.Enable()

    def _on_cfg_close(self, event):
        dialog_text = "Save settings?"
        dial = wx.MessageDialog(None, dialog_text, 'Question',
                                wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
        ret = dial.ShowModal()
        if ret == wx.ID_YES:
            self.cfg.save_config()
        else:
            pass
        
        self.cfg_frame.Show(False)

    def _on_plot_close(self, event):
        # Run when plot window is closed
        self.on_mainmenu(None)

    def _reset_session(self):
        self.current.y_index = 0
        self.current.peak = (0, 0)
        self.current.target = False
        self.current.str_hit = False
        self.current.timeline = False
        self.current.amplline = False
        self.current.peaks = []
        self.current.history = []
        self.current.baseline = 0
        self.current.calib_index = 0
        self.current.trial_index = 0
        self.current.mode = 0
        self.current.alive = False


"""--------------------------------------------------------------------------------------------------
    main() function called when run from the command line
--------------------------------------------------------------------------------------------------"""
def main():
    app = MyApp()
    app.MainLoop()

if __name__ == '__main__':
    main()

# EOF
